//**************************************************************************************
// MediaFrame is an Open Source streaming media platform in Java 
// which provides a fast, easy to implement and extremely small applet 
// that enables to view your audio/video content without having 
// to rely on external player applications or bulky plug-ins.
//
//--------------------------------------------------------------------------------------
//
// We changed a lot of code and added a lot of functionality.
// This includes, but not limited to, the following changes:
// 1. The project was renamed to MediaFrame;
// 2. The connection speed detection procedure was added;
// 3. The JavaScript API functions were added;
// 4. The pre and post image support functionality was added;
// 5. The ability to save movie into the local disk was added;
// 6. The inner buffer for a movie file was added;
// 7. The click-through functionality was added;    
// 8. The .zip files support was added;    
// 9. The realtime feedback agent functionality was added.    
// For the full list of the current functionality please visit the following web page:
// http://mediaframe.org/
//    
// 06 Jul 2002 - 19 Dec 2004 Konstantin Belous, Oleg Lebedev
//
//--------------------------------------------------------------------------------------
//
//								  "io_tool.java"
//
// This file contains some special IO methods. They are necessary because the MPEG
// stream is bit oriented. The io tool is constituted by a DataInputStream on to of
// a BufferedInputStream.  Furthermore a bit shift register is supplied.
//
// The "io_tool" has the special task to assist the switching between MPEG layer I and
// MPEG layer II. During analysing	MPEG layer II it counts the remaining bytes for
// layer II and initiates a switch back to layer I if the counter is (near) zero.
// (See also comment in "MPEG_scan.java"!)
//
// See also: http://rnvs.informatik.tu-chemnitz.de/~ja/MPEG/HTML/imp_asp.html
//
//--------------------------------------------------------------------------------------
//
//				Joerg Anders, TU Chemnitz, Fakultaet fuer Informatik, GERMANY
//				ja@informatik.tu-chemnitz.de
//
//
//--------------------------------------------------------------------------------------
//
// This program is free software; you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the Free Software
// Foundation; either version 2 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with this
// program; (See "LICENSE.GPL"). If not, write to the Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA	02111-1307, USA.
//
//--------------------------------------------------------------------------------------
//
// If the program runs as Java applet it isn't "interactive" in the sense of the GNU
// General Public License. So paragraph 2c doesn't apply.
//
//**************************************************************************************

package mediaframe.mpeg1;

import java.io.*;		// BufferedInputStream, DataInputStream, IOException

/**
 * The <code>io_tool</code> class implements the functionality 
 * for accessing to the input movie's data stream as a bit stream.
 * It contains methods for reading, skiping, matching with the pattern, 
 * giving "back" next/previous "n" bits from/to the data stream.
 */
public class io_tool {
	
	/** The type of the stream to read (audio/video). */
	private int _type;
	/** The Audio type of the input stream. */ 
	public static final int TYPE_AUDIO = 0;
	/** The Video type of the input stream. */ 
	public static final int TYPE_VIDEO = 1;	
	/** The bit-"pointer", contains the number of the current bit in the bit shift register. */
	int bit_pos = 0;					  
	/** The bit shift register. */
	private long longword = 0L;				 
	/** <tt>True</tt> if the end of the file has been reached. */
	private boolean eof = false;

	/** The current <code>DataBuffer</code> with the movie's data to read. */
	private DataBuffer buffer = null;

	/** The movie's data of the current data block. */
	private long[] Data;
	/** The size of the current data block in bytes. */
	private int    BlockSize = 0;
	/** The byte-"pointer", contains the number of the current byte in the data block. */
	private int    BlockPos  = 0;

	/**
	 * Constructs an <code>io_tool</code> object with the specified values.
	 * @param type the type of the input stream.
	 * @param buffer the buffer with the movie's data.
	 */
	public io_tool(int type, DataBuffer buffer) {
		_type = type;
		this.buffer = buffer;
		buffer.initReaderSide();
	}

	/**
	 * Gets the current <code>DataBuffer</code> with the movie's data for reading. 
	 * @return the current <code>DataBuffer</code> object.
	 */
	public final DataBuffer getBuffer() {
		return buffer;
	}


/*
	public final void close() throws IOException {
		Data = null;
		BlockPos = 0;
		BlockSize = 0;
	} 
*/
	
	/** 
	 * Returns the end of the file flag value.
	 * @return <tt>True</tt> if the end of the file has been reached.
	 **/
	public final boolean is_eof() {
		return eof; 
	}

	/**
	 * The method "skip(long n)" skips over <code>n</code> bytes but takes into account that
	 * some of them are already in shift register.
	 * @param n the number of bytes to skip.
	 * @exception InterruptedException raises if another process interrupts the current process.
	 * @exception EOFException raises if the end of the file has been reached.
	 */
	public final void skip(long n) throws InterruptedException, EOFException {
		int k = bit_pos / 8;				// How many bytes are already in shift register ?
		n -= k;						// substract from whole count
		bit_pos -= k * 8;				// skip over the bits in shift register
		
		BlockPos = BlockPos - (int)n;

		while(BlockPos >= BlockSize) {
			try
			{
				int diff = BlockPos -  BlockSize;
				nextBlock();
				BlockPos = diff;

			}
			catch (EOFException e) {
				eof = true;
				throw new EOFException(e.toString());
			}
		}
	}

	/**
	 * The method "get_long" grabs "bytes" from data input stream
	 * into the shift register "longword".
	 * @exception InterruptedException raises if another process interrupts the current process.
	 * @exception EOFException raises if the end of the file has been reached.
	 */
	private final void get_long() throws InterruptedException, EOFException {

		if(BlockPos + 8 > BlockSize) {

			try {

				long Byte = 0;
				int index = 0;
				int shift = 0;

				for(int i = 0; i < 4; i++) {
					if(BlockPos >= BlockSize)
						nextBlock();
					index = BlockPos >> 3;
					shift = (7 - (BlockPos & 7)) << 3;
				
					Byte = (Data[index] >> shift) & 0xFFL;

					longword = (longword << 8) | Byte;

					BlockPos++;
				}
			}
			catch (EOFException e) {
				eof = true;
				bit_pos += 32;
				longword <<= 32;
//				System.out.flush();
//				System.out.println("EOF FOUND!");
//				e.printStackTrace(System.out);
				throw new EOFException(e.toString());
				
			}

		} else {

			longword = longword << 32 | (Data[BlockPos >> 3] >> (32 - ((BlockPos & 7) << 3))) & 0xFFFFFFFFL;

			BlockPos += 4;

		}

		bit_pos += 32;
	}

	/**
	 * The method "get_bits" gets the next "n" bits from shift register
	 * interprets them as integer and returns this integer value.
	 * @param n the amount of bits to get.
	 * @return the next "n" bits from shift register.
	 * @exception InterruptedException raises if another process interrupts the current process.
	 * @exception EOFException raises if the end of the file has been reached.
	 */
	public final int get_bits(int n) throws InterruptedException, EOFException {
		if (bit_pos < n) get_long();

		bit_pos -= n; // correct bit "pointer"
		// FIXME: array lookup shift faster??
		return (int) ((longword >>> bit_pos) & ((1L << n) - 1L));
	}

	/**
	 * The method "skip_bits" skips the next "n" bits from shift register.
	 * This method created because too much time was being wasted doing the
	 * math in get_bits when the result was just going to be thrown out.
	 * @param n the amount of bits to skip.
	 * @exception InterruptedException raises if another process interrupts the current process.
	 * @exception EOFException raises if the end of the file has been reached.
	 */
	public final void skip_bits(int n) throws InterruptedException, EOFException {
		if (bit_pos < n) get_long();
		bit_pos -= n; // correct bit "pointer"
	}

	/**
	 * The method "next_bits" checks whether the next "n" bits match the
	 * "pattern". If so it returns <tt>true</tt>; otherwise <tt>false</tt>.
	 * @param pattern the pattern to match. 
	 * @param n the number of bits in the pattern.
	 * @return <tt>true</tt> if the next "n" bits match the "pattern".
	 * @exception InterruptedException raises if another process interrupts the current process.
	 * @exception EOFException raises if the end of the file has been reached.
	 */ 
	/* Note: This method changes the bit "pointer" physically BUT NOT logically !!! */
	public final boolean next_bits(int pattern, int n) throws InterruptedException, EOFException {
		if (bit_pos < n) get_long();
		return (int)((longword >>> (bit_pos - n)) & ((1L << n) - 1L)) == pattern;
	}

	/**
	 * The method "peek_bits" is like get_bits except it leaves the
	 * bits in the shift register.  Use it when you want to read
	 * the same bits more than once.
	 * @param n the number of bits to get.
	 * @return the next "n" bits from shift register.
	 * @exception InterruptedException raises if another process interrupts the current process.
	 * @exception EOFException raises if the end of the file has been reached.
	 */
	public final int peek_bits(int n) throws InterruptedException, EOFException {
		if (bit_pos < n) get_long();
		return (int)((longword >>> (bit_pos - n)) & ((1L << n) - 1L));
	}

	/**
	 * The method "unget_bits" gives "n" bits back to the IO system. Because
	 * "n" is always less than 32 this can be performed by a simple
	 * correction of the bit "pointer".
	 * @param n the number of bits to unget.
	 */ 
	/* Note: Do not unget more then 32 bits... !! */
	public final void unget_bits(int n) {
		bit_pos += n;
	}

	/**
	 * The method "next_start_code" aligns the bit "pointer" to the next
	 * byte and tries to find the next MPEG start code. Because (only) start
	 * codes are made of a 24-bit ONE the method searches such a pattern.
	 * (see also: ISO 11172-2)
	 * @exception InterruptedException raises if another process interrupts the current process.
	 * @exception EOFException raises if the end of the file has been reached.
	 */ 
	public final void next_start_code() throws InterruptedException, EOFException {
		if ((bit_pos & 0x7) != 0) 
			bit_pos &= ~0x7;

		while (!next_bits(0x1, 24) && !eof) skip_bits(8);
	} 

	/**
	 * Gets next block of movie's data from the buffer and stores it't data into the local variables.
	 * @exception InterruptedException raises if another process interrupts the current process.
	 * @exception EOFException raises if the end of the file has been reached.
	 */ 
	private final void nextBlock() throws EOFException, InterruptedException
	{

		while(BlockPos >= BlockSize) {
			DataBlock DB = buffer.readNextBlock();

			if(DB != null) {
				BlockSize = DB.size;
				Data      = DB.data;
				BlockPos  = 0;
			}
		}
	}
}

